package cs275.ants.graphics;

import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;

import javax.swing.ImageIcon;

import com.google.common.collect.Lists;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.FirstPersonHandler;
import com.jme.input.ThirdPersonHandler;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.FaultFractalHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;

import cs275.ants.graph.Maze;
import cs275.ants.graph.MazeCell;
import cs275.ants.graph.MazeCell.Wall;
import cs275.ants.graph.base.Graph;
import cs275.ants.graph.base.GraphEdge;
import cs275.ants.graph.MazeGenerator;
import cs275.ants.graph.antgraph.AntGraphEdge;
import cs275.ants.graph.base.Graph;
import cs275.ants.graph.base.GraphEdge;

public class Graphics extends SimpleGame {
	private static final Logger logger = Logger.getLogger(Graphics.class
			.getName());
	private static final int kMazeWallHeight = 5;
	private static final int kMazeWallThickness = 1;
	private static final int kMapSize = 257;
	
	private static final int kMazeNumCol = 8; 
	private static final int kMazeNumRow = 8; 
	// private TriMesh m_character;
	Sphere sphere;
	private TerrainPage terrain;
	List<Character> ants;
	List<Particle> pheromones;

	private MazeGenerator mazeGenerator;
	private CharacterGenerator characterGenerator;
	private ParticleGenerator particleGenerator;
	private Maze maze;
	private TextureState brickWallTextureState;
	private Random randomGenerator = new Random((new Date()).getTime());

	private float cellScreenWidth;
	private float cellScreenHeight;
	private Graph graph;
	/**
	 * builds the scene.
	 * 
	 * @see com.jme.app.SimpleGame#initGame()
	 */
	public Graphics(MazeGenerator mazeGenerator,
			CharacterGenerator characterGenerator,
			ParticleGenerator particleGenerator) {
		this.ants = Lists.newArrayList();
		this.pheromones = Lists.newArrayList();
		this.mazeGenerator = mazeGenerator;
		this.characterGenerator = characterGenerator;
		this.particleGenerator=particleGenerator;
	}

	protected TextureState getBrickWallTextureState() { 
		if (this.brickWallTextureState == null) { 
			this.brickWallTextureState = display.getRenderer().createTextureState();
			URL brickLoc = Graphics.class.getClassLoader().getResource("resources/textures/brick.jpg");
			Texture t = TextureManager.loadTexture(
							brickLoc,
							Texture.MinificationFilter.BilinearNearestMipMap,
							Texture.MagnificationFilter.Bilinear);
			this.brickWallTextureState.setTexture(t);		
		}
		return this.brickWallTextureState;
	}
	
	protected void simpleInitGame() {
		display.setTitle("Ant Colony");		
		this.maze = mazeGenerator.generate(kMazeNumCol, kMazeNumRow);
		MazeCell mazeCell[][]=maze.getCells();
		mazeCell[maze.getWidth()-1][maze.getHeight()-1].setHasFood(true);
		for(int i=0;i<mazeCell.length;i++)
			for(int j=0;j<mazeCell[i].length;j++){
				mazeCell[i][j].setParticle(particleGenerator.generate());
			}
		cellScreenWidth = kMapSize / maze.getWidth();
		cellScreenHeight = kMapSize / maze.getHeight();
		setupTerrain();
		setupMaze();
		setupParticle();
		setupCharacter();
		setupInput();
		setupCamera();
	}

	protected void simpleUpdate() {
		// chaser.update(tpf);
		cam.update();

		for (Character ant : ants) {
			TriMesh m_character = ant.getModel();
			Vector3f positionVector = m_character.getLocalTranslation();
			float characterMinHeight = terrain.getHeight(positionVector);
			if (!Float.isInfinite(characterMinHeight)
					&& !Float.isNaN(characterMinHeight)) {
				m_character.setLocalTranslation(positionVector.x,
						characterMinHeight, positionVector.z);
			}
			ant.update();
		}

		graph = maze.getGraph();
		for(GraphEdge edge : graph.getAllEdges()) {
			AntGraphEdge antEdge = (AntGraphEdge) edge;
			antEdge.updatePheromone();
		}
//		System.out.println(graph.getAllEdges());

		MazeCell pheromoneTrail[][]=maze.getCells();
		Graph mazeGraph=maze.getGraph();
		Set<GraphEdge> edges=mazeGraph.getAllEdges();
		double maxLevel=Double.MIN_VALUE;
		double minLevel=Double.MAX_VALUE;
		for(GraphEdge edge : edges){
			if(edge.getCost()!=Double.MAX_VALUE){
				maxLevel=Math.max(edge.getCost(), maxLevel);
				minLevel=Math.min(edge.getCost(), minLevel);
			}
		}
		for(int i=0;i<pheromoneTrail.length;i++){
			for(int j=0;j<pheromoneTrail[i].length;j++){
				pheromoneTrail[i][j].getParticle().resetPheromoneLevel();
			}
		}
		for(GraphEdge edge : edges){
			int node1X=edge.getNode1().getCoordinates().getX();
			int node1Y=edge.getNode1().getCoordinates().getY();
			
			int node2X=edge.getNode2().getCoordinates().getX();
			int node2Y=edge.getNode2().getCoordinates().getY();
			float level;
			if(maxLevel-minLevel!=0)
				level=(float)((edge.getCost()-minLevel)/((maxLevel-minLevel)*4));
			else level=0;
			if(level>1 || edge.getCost()==Double.MAX_VALUE)
				level=1;
			pheromoneTrail[node1Y][node1X].getParticle().addPheromoneLevel(level);
			pheromoneTrail[node2Y][node2X].getParticle().addPheromoneLevel(level);

//			System.out.println(pheromoneTrail[node1X][node1Y].getParticle().getParticleMesh().getReleaseRate());
		}
	}
	
	private void setupCharacter() {
		Character ant;
//		ant = characterGenerator.generate();
//		rootNode.attachChild(ant.getModel());
//
//		sphere = createSphere();
//
//		rootNode.setLightCombineMode(com.jme.scene.Spatial.LightCombineMode.Off);
//		rootNode.attachChild(sphere);
//		ant.setDestination(sphere.getWorldTranslation());
//		ants.add(ant);
		
		
		// ant size
		float scale = Math.min(cellScreenWidth, cellScreenHeight) * 0.3f;
		
		// add other ants 
		for (int i=0; i<30; i++) { 
			ant = characterGenerator.generate(); 
			ant.getModel().setLocalScale(scale);
			ant.getModel().setLocalTranslation(
					(float) Math.random() * cellScreenWidth,
					ant.getModel().getLocalTranslation().y,
					(float)Math.random() * cellScreenHeight);
			ant.setUp(maze, this);
			rootNode.attachChild(ant.getModel());
			//ant.setDestination(this.getCellScreenCoordinateWithRandomOffset(3,8));	
			ants.add(ant);
		}	
	}

	private void setupParticle() {
		
		MazeCell pheromoneTrail[][]=maze.getCells();
		for(int i=0;i<pheromoneTrail.length;i++)
			for(int j=0;j<pheromoneTrail[i].length;j++){
				Particle particle=pheromoneTrail[i][j].getParticle();
				Vector3f coordinate=getCellScreenCoordinate(i, j);
				coordinate.y=terrain.getHeight(particle.getParticleMesh().getLocalTranslation())-10;
				particle.getParticleMesh().setLocalTranslation(coordinate);
				float particleSize=(float)(kMapSize/(64.0*kMazeNumCol/10));
				particle.getParticleMesh().setLocalScale(new Vector3f(particleSize,1,particleSize));
				rootNode.attachChild(particle.getParticleMesh());
			}
	}

	private Sphere createSphere() {
		Sphere sphere = new Sphere("My sphere", 10, 10, 1f);
		sphere.setLocalScale(10f);
		// Do bounds for the sphere, but we'll use a BoundingBox this time
		sphere.setModelBound(new BoundingBox());
		sphere.updateModelBound();
		// Give the sphere random colors
		sphere.setRandomColors();
		sphere.getLocalTranslation().x = 140;
		sphere.getLocalTranslation().z = 50;
		sphere.getLocalTranslation().y += terrain.getHeight(sphere
				.getLocalTranslation())
				+ sphere.getRadius() * 10f;
		return sphere;
	}
	
	private void addVerticalWall(Vector3f loc, float wallWidth) { 			
		
		Box wall = new Box();
		wall.setRenderState(this.getBrickWallTextureState());
		wall.updateGeometry(loc, kMazeWallThickness, kMazeWallHeight, wallWidth/2);
		rootNode.attachChild(wall);
	}
	
	private void addHorizontalWall(Vector3f loc, float wallWidth) { 
		Box wall = new Box();
		wall.setRenderState(this.getBrickWallTextureState());
		wall.updateGeometry(loc, wallWidth/2, kMazeWallHeight, kMazeWallThickness);
		rootNode.attachChild(wall);
	}
	public Vector3f getCellScreenCoordinate(int row, int col) { 
		return new Vector3f(col*cellScreenWidth, 0, row*cellScreenHeight);
	}

	public Vector3f getCellScreenCoordinateWithRandomOffset(int row, int col) { 
		float offsetX = (randomGenerator.nextFloat()-0.6f)*2; // from  -1.0 - 1.0
		float offsetZ = (randomGenerator.nextFloat()-0.6f)*2;
		return new Vector3f((col+0.5f)*cellScreenWidth+offsetX, 0, (row+0.5f)*cellScreenHeight+offsetZ);
	}
	
	private void drawFood(int cellRow, int cellCol) {
		Vector3f loc = getCellScreenCoordinate(cellRow, cellCol);
		Box box = new Box("Food", loc, 3.0f, 8.0f, 3.0f);
		box.setModelBound(new BoundingBox());
		box.updateModelBound();
		box.setRandomColors();
		rootNode.attachChild(box);
	}
	
	private void setupMaze() {
		
		MazeCell[][] cells = this.maze.getCells();
						
		
		for (int row=0; row<maze.getHeight(); row++) {
			for (int col=0; col<maze.getWidth(); col++) { 
				
				MazeCell currentCell = cells[row][col];
				if (currentCell.hasFood()) {
					drawFood(row, col);
				}
				if (currentCell.hasWall(Wall.LEFT)) { 
					addVerticalWall(
							new Vector3f(cellScreenWidth*(col-0.5f),
									 	 0, 
										 cellScreenHeight*row), 
							cellScreenHeight);					
				}
				if (currentCell.hasWall(Wall.RIGHT)) { 
					addVerticalWall(
							new Vector3f(cellScreenWidth*(col+0.5f), 
										0, 
										cellScreenHeight*row), 
							cellScreenHeight);					
				}
				if (currentCell.hasWall(Wall.UP)) { 
					addHorizontalWall(
							new Vector3f(cellScreenWidth*col, 
										0, 
										cellScreenHeight*(row-0.5f)), 
							cellScreenWidth);					
				}
				if (currentCell.hasWall(Wall.DOWN)) { 
					addHorizontalWall(
							new Vector3f(cellScreenWidth*col, 
										 0, 
									 	 cellScreenHeight*(row+0.5f)), 
							cellScreenWidth);					
				}
				
			}
		}
	}
	
	private AbstractHeightMap createHeightMap() {
//		heightMap.setHeightAtPoint(10, 1, 1);
		FaultFractalHeightMap heightMap = 
			new FaultFractalHeightMap(
					kMapSize, 
					32, 0, 255, 0.75f);
			
		for (int i=0; i<kMapSize; i++) { 
			for (int j=0; j<kMapSize; j++) { 
				heightMap.setHeightAtPoint(0, i, j);				
			}
		}
		
		return heightMap;
	}	
	
	// Change this to Flat Terrain
	private void setupTerrain() {
		rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

		display.getRenderer().setBackgroundColor(
				new ColorRGBA(0.5f, 0.5f, 0.5f, 1));

		DirectionalLight dr = new DirectionalLight();
		dr.setEnabled(true);
		dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
		dr.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
		dr.setDirection(new Vector3f(0.5f, -0.5f, 0));

		CullState cs = display.getRenderer().createCullState();
		cs.setCullFace(CullState.Face.Back);
		cs.setEnabled(true);
		rootNode.setRenderState(cs);

		lightState.detachAll();
		lightState.attach(dr);

//		FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 32, 0,
//				255, 0.75f);
		
		AbstractHeightMap heightMap = createHeightMap();
	
		Vector3f terrainScale = new Vector3f(10, 1, 10);
		heightMap.setHeightScale(0.001f);
		terrain = new TerrainPage("Terrain", 33, heightMap.getSize(),
				terrainScale, heightMap.getHeightMap());

		terrain.setDetailTexture(1, 16);
		rootNode.attachChild(terrain);

		ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
				heightMap);
		pt.addTexture(new ImageIcon(Graphics.class.getClassLoader()
				.getResource("resources/textures/grassb.png")), -128, 0, 128);
		pt.addTexture(new ImageIcon(Graphics.class.getClassLoader()
				.getResource("resources/textures/dirt.jpg")), 0, 128, 255);
		pt.addTexture(new ImageIcon(Graphics.class.getClassLoader()
				.getResource("resources/textures/highest.jpg")), 128, 255, 384);

		pt.createTexture(512);

		TextureState ts = display.getRenderer().createTextureState();
		ts.setEnabled(true);
		Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
				Texture.MinificationFilter.Trilinear,
				Texture.MagnificationFilter.Bilinear, true);
		ts.setTexture(t1, 0);

		Texture t2 = TextureManager.loadTexture(Graphics.class.getClassLoader()
				.getResource("resources/textures/Detail.jpg"),
				Texture.MinificationFilter.Trilinear,
				Texture.MagnificationFilter.Bilinear);
		ts.setTexture(t2, 1);
		t2.setWrap(Texture.WrapMode.Repeat);

		t1.setApply(Texture.ApplyMode.Combine);
		t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
		t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
		t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
		t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
		t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

		t2.setApply(Texture.ApplyMode.Combine);
		t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
		t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
		t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
		t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
		t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
		rootNode.setRenderState(ts);

		FogState fs = display.getRenderer().createFogState();
		fs.setDensity(0.5f);
		fs.setEnabled(true);
		fs.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
		fs.setEnd(1000);
		fs.setStart(500);
		fs.setDensityFunction(FogState.DensityFunction.Linear);
		fs.setQuality(FogState.Quality.PerVertex);
		rootNode.setRenderState(fs);
	}

	private void setupCamera() {
		input = new FirstPersonHandler(cam, 50, 1);
		Vector3f firstAntLocation = ants.get(0).getModel()
				.getWorldTranslation().clone();
		cam.setLocation(firstAntLocation);
		cam.getLocation().y = terrain.getHeight(cam.getLocation()) + 200;
		//System.out.println(firstAntLocation);
		cam.lookAt(new Vector3f(0, 0, 0), new Vector3f(0, 0, -1));
		cam.update();
	}

	private void setupInput() {
		HashMap<String, Object> handlerProps = new HashMap<String, Object>();
		handlerProps.put(ThirdPersonHandler.PROP_DOGRADUAL, "true");
		handlerProps.put(ThirdPersonHandler.PROP_TURNSPEED, ""
				+ (1.0f * FastMath.PI));
		handlerProps.put(ThirdPersonHandler.PROP_LOCKBACKWARDS, "false");
		handlerProps.put(ThirdPersonHandler.PROP_CAMERAALIGNEDMOVE, "true");
		// input = new ThirdPersonHandler(m_character, cam, handlerProps);
		input.setActionSpeed(100f);
	}
}
